/*
 * Copyright (C) 2009 Anatoly Zenkov
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the author be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */
package com.kartoshka.core.output {
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.geom.Rectangle;
	import flash.system.Capabilities;
	import flash.system.System;
	import flash.text.StyleSheet;
	import flash.text.TextField;
	import flash.utils.Timer;
	import flash.utils.getTimer;

	/**
	 * @author AnatolyZenkov
	 */
	public class Meter extends Sprite {
		private const BITMAPDATA_HEIGHT : int = 70;
		private const GRAY : uint = 0x7C8588;
		private const LIGHT_GRAY : uint = 0x979EA0;
		private const SHIFTED_LIGHT_GRAY : uint = 0x979EA1;
		private const GREEN : uint = 0xB3FB00;
		private const WHITE : uint = 0xFFFFFF;
		private const BASE_MEMORY : uint = 134217728;
		private var _textField : TextField;
		private var _timer : Timer;
		private var _frameTime : int;
		private var _fillRect : Rectangle;
		private var _clock : Boolean = false;
		private var _outputBitmapData : BitmapData;
		private var _baseMemory : uint;
		private var _baseMemoryCounter : uint;
		private var _maxMemory : uint = 0;
		private var _xml : XML;
		private var _fpsCounter : int = 0;
		private var _relativeX : Number = 1;
		private var _relativeY : Number = 0;
		private var _dx : Number;
		private var _dy : Number;

		/**
		 * Simple to use application workflow widget. Allows to monitor actual frame rate,
		 * used memory and maximum rate of memory that was used. Also provide player version data.
		 * Has ability to be placed anywhere on application screen "on-the-go" by moving by mouse.
		 * Also can be quickly hiden and shown by pressing Cntr+Shift+M key combination on the keyboard.
		 * Therefore you can leave this meter in release version of project but set its visible parameter
		 * to false and reveal it further by demand.
		 */
		public function Meter() {
			mouseChildren = false;
			tabChildren = false;
			
			_outputBitmapData = new BitmapData(100, BITMAPDATA_HEIGHT, false, 0xFF000000);
			_textField = new TextField();
			_textField.background = true;
			_textField.backgroundColor = 0;
			_textField.height = 50;
			_textField.multiline = true;
			_textField.condenseWhite = true;
			addChild(_textField);
			
			var style : StyleSheet = new StyleSheet();
			style.setStyle("xml", {fontSize:10, fontFamily:"_typewriter"});
			style.setStyle("fps", {color:hexColor(WHITE)});
			style.setStyle("mem", {color:hexColor(GREEN)});
			style.setStyle("max", {color:hexColor(SHIFTED_LIGHT_GRAY)});
			style.setStyle("ver", {color:hexColor(GRAY)});
			_textField.styleSheet = style;

			_xml = <xml><fps/><mem/><max/><ver/></xml>;
			_xml["version"] = Capabilities.version;

			_fillRect = new Rectangle();			
			addChild(new Bitmap(_outputBitmapData)).y = _textField.height;
			
			_timer = new Timer(1000);
			_timer.addEventListener(TimerEvent.TIMER, perSecondTimerHandler);
			
			_baseMemory = BASE_MEMORY;
			_baseMemoryCounter = 1;
			
			addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
			addEventListener(Event.REMOVED_FROM_STAGE, removedFromStageHandler);
			addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
		}

		private function mouseDownHandler(event : MouseEvent) : void {
			_dx = mouseX;
			_dy = mouseY;
			stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
			stage.addEventListener(Event.MOUSE_LEAVE, mouseUpHandler);
			stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
		}

		private function mouseMoveHandler(event : MouseEvent) : void {
			var tx : Number = stage.mouseX - _dx;
			var ty : Number = stage.mouseY - _dy;
			_relativeX = Math.min(Math.max(tx / (stage.stageWidth - width), 0), 1);
			_relativeY = Math.min(Math.max(ty / (stage.stageHeight - height), 0), 1);
			if (_relativeX < 0.01) _relativeX = 0;
			if (_relativeY < 0.01) _relativeY = 0;
			if (_relativeX > 0.99) _relativeX = 1;
			if (_relativeY > 0.99) _relativeY = 1;
			resizeHandler(null);
			event.updateAfterEvent();
		}

		private function mouseUpHandler(event : Event) : void {
			stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
			stage.removeEventListener(Event.MOUSE_LEAVE, mouseUpHandler);
			stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
		}

		private function hexColor(color : uint) : String {
			return "#" + color.toString(16);
		}

		private function perSecondTimerHandler(event : TimerEvent) : void {
			_clock = true;
			_xml["fps"] = "FPS:" + _fpsCounter + "/" + stage.frameRate;
			_xml["mem"] = "MEM:" + (System.totalMemory * 0.00000095367431640625).toFixed(2) + " Mb";
			_xml["max"] = "MAX:" + (_maxMemory * 0.00000095367431640625).toFixed(2) + " Mb";
			_textField.htmlText = _xml;
			_fpsCounter = 0;
		}

		private function addedToStageHandler(event : Event) : void {
			if (event.target != this) return;
			activeMode = visible;
			stage.addEventListener(Event.RESIZE, resizeHandler);
			resizeHandler(null);
			stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
			parent.addEventListener(Event.ADDED, parent_addedHandler);
		}
		
		private function parent_addedHandler(event : Event) : void {
			var n:int = parent.getChildIndex(this);
			while(n < parent.numChildren-1) {
				parent.swapChildrenAt(n, n+1);
				n = parent.getChildIndex(this);
			}
		}

		private function keyDownHandler(event : KeyboardEvent) : void {
			if (!event.shiftKey) return;
			if (!event.ctrlKey) return;
			if (event.keyCode != 13) return;
			visible = !visible;
		}

		private function resizeHandler(event : Event) : void {
			x = int((stage.stageWidth - width) * _relativeX);
			y = int((stage.stageHeight - height) * _relativeY);
		}

		private function removedFromStageHandler(event : Event) : void {
			if (event.target != this) return;
			activeMode = false;
		}

		override public function set visible(value : Boolean) : void {
			super.visible = value;
			if (stage) activeMode = visible;
		}

		private function set activeMode(value : Boolean) : void {
			if (value) {
				_xml["fps"] = "FPS:--/--";
				_xml["mem"] = "MEM:--.-- Mb";
				_xml["max"] = "MAX:--.-- Mb";
				_textField.htmlText = _xml;
				_frameTime = getTimer();
				_timer.reset();
				_timer.start();
				_fpsCounter = 0;
				_outputBitmapData.fillRect(_outputBitmapData.rect, 0);
				addEventListener(Event.ENTER_FRAME, enterFrameHandler);
			} else {
				removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
				_timer.stop();
			}
		}

		private function enterFrameHandler(event : Event) : void {
			var fps : Number = 1000 / (getTimer() - _frameTime) / stage.frameRate;
			_frameTime = getTimer();
			var offset : int = Math.round(1 / fps);
			_outputBitmapData.scroll(offset, 0);
			
			_fillRect.y = 0;
			_fillRect.width = offset;
			_fillRect.height = BITMAPDATA_HEIGHT;
			_outputBitmapData.fillRect(_fillRect, 0);
			
			_fillRect.y = BITMAPDATA_HEIGHT * (1 - fps);
			_fillRect.height -= _fillRect.y;
			_outputBitmapData.fillRect(_fillRect, GRAY);
			
			if (_clock) {
				_fillRect.x = _fillRect.width - 1;
				_fillRect.width = 1;
				_outputBitmapData.fillRect(_fillRect, LIGHT_GRAY);
				_fillRect.x = 0;
				_fillRect.width = offset;
				_clock = false;
			}
			
			if (System.totalMemory > _baseMemory) increaseMaxMemory();
			_maxMemory = Math.max(System.totalMemory, _maxMemory);
			_fillRect.height = 1;
			_fillRect.y = BITMAPDATA_HEIGHT - _maxMemory / _baseMemory * BITMAPDATA_HEIGHT;
			_outputBitmapData.fillRect(_fillRect, SHIFTED_LIGHT_GRAY);
			_fillRect.y = BITMAPDATA_HEIGHT - System.totalMemory / _baseMemory * BITMAPDATA_HEIGHT;
			_outputBitmapData.fillRect(_fillRect, GREEN);
			_fpsCounter++;
		}

		private function increaseMaxMemory() : void {
			var py : int;
			var px : int;
			var color : uint;
			_outputBitmapData.lock();
			for (px = 1;px < _outputBitmapData.width; px++) {
				for (py = 0;py < BITMAPDATA_HEIGHT; py++) {
					color = _outputBitmapData.getPixel(px, py);
					if (color == SHIFTED_LIGHT_GRAY) {
						_outputBitmapData.setPixel(px, py, _outputBitmapData.getPixel(px, py - 1));
						_outputBitmapData.setPixel(px, BITMAPDATA_HEIGHT - (BITMAPDATA_HEIGHT - py) * _baseMemoryCounter / (_baseMemoryCounter + 1), SHIFTED_LIGHT_GRAY);
					}
					if (color == GREEN) {
						_outputBitmapData.setPixel(px, py, _outputBitmapData.getPixel(px, py + 1));
						_outputBitmapData.setPixel(px, BITMAPDATA_HEIGHT - (BITMAPDATA_HEIGHT - py) * _baseMemoryCounter / (_baseMemoryCounter + 1), GREEN);
						break;
					}
				}
			}
			_outputBitmapData.unlock();
			_baseMemoryCounter++;
			_baseMemory += BASE_MEMORY;
		}
	}
}